package org.apache.maven.plugins.enforcer.util;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.enforcer.rule.api.EnforcerRule;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.InputLocation;
import org.apache.maven.model.InputSource;
import org.apache.maven.plugin.DebugConfigurationListener;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugins.enforcer.EnforceMojo;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.converters.composite.AbstractCollectionConverter;
import org.codehaus.plexus.component.configurator.converters.composite.ArrayConverter;
import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup;
import org.codehaus.plexus.component.configurator.converters.special.ClassRealmConverter;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.logging.console.ConsoleLogger;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.XmlStreamReader;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder.InputLocationBuilder;
import org.codehaus.plexus.util.xml.pull.EntityReplacementMap;
import org.codehaus.plexus.util.xml.pull.MXParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;

/**
 * This goal executes the defined enforcer-rules once per module.
 *
 * @author Ares
 */
public class RulesFileParseUtil
{

  public static EnforcerRule[] createRulesFromFile( String ruleFile, Object mojo,
      MojoExecution mojoExecution, MavenSession session ) throws MojoExecutionException
  {
    List<EnforcerRule> enforcerRuleList = new ArrayList<>( );

    try ( XmlStreamReader reader = ReaderFactory.newXmlReader( new FileInputStream( ruleFile ) ) )
    {
      XmlPullParser parser = new MXParser( EntityReplacementMap.defaultEntityReplacementMap );
      parser.setInput( reader );
      InputLocation location = new InputLocation( parser.getLineNumber( ), parser.getColumnNumber( ),
          new InputSource( ) );
      Xpp3Dom configuration = Xpp3DomBuilder.build( parser, true,
          new Xpp3DomBuilderInputLocationBuilder( location ) );
      PlexusConfiguration plexusConfiguration = new XmlPlexusConfiguration( configuration );

      MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor( );
      ClassRealm classRealm = mojoDescriptor.getPluginDescriptor( ).getClassRealm( );
      ConverterLookup converterLookup = new DefaultConverterLookup( );
      converterLookup.registerConverter( new ClassRealmConverter( classRealm ) );
      ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator( session, mojoExecution );
      ConfigurationListener listener = new DebugConfigurationListener( new ConsoleLogger() );
      ValidatingConfigurationListener validator =
          new ValidatingConfigurationListener( mojo, mojoDescriptor, listener );

      ArrayConverter converter = new ArrayConverter();
      Method method = AbstractCollectionConverter.class.getDeclaredMethod( "fromChildren",
          ConverterLookup.class, PlexusConfiguration.class, Class.class, Class.class, ClassLoader.class,
          ExpressionEvaluator.class, ConfigurationListener.class, Class.class );
      method.setAccessible( true );
      Collection<EnforcerRule> collection = ( Collection ) method.invoke( converter, converterLookup,
          plexusConfiguration, EnforcerRule[].class, EnforceMojo.class,
          classRealm, expressionEvaluator, validator, EnforcerRule.class );
      enforcerRuleList.addAll( collection );
    }
    catch ( IOException e )
    {
      throw new MojoExecutionException( "RuleFile: " + ruleFile + " is not exist" );
    }
    catch ( Throwable e )
    {
      throw new MojoExecutionException( e.getMessage() );
    }

    return enforcerRuleList.toArray( new EnforcerRule[0] );
  }

  private static class Xpp3DomBuilderInputLocationBuilder implements InputLocationBuilder
  {
    private final InputLocation rootLocation;

    Xpp3DomBuilderInputLocationBuilder( InputLocation rootLocation )
    {
      this.rootLocation = rootLocation;
    }

    @Override
    public Object toInputLocation( XmlPullParser parser )
    {
      return new InputLocation( parser.getLineNumber( ), parser.getColumnNumber( ), rootLocation.getSource( ) );
    }

  }

  static class ValidatingConfigurationListener
      implements ConfigurationListener
  {

    private final Object mojo;

    private final ConfigurationListener delegate;

    private final Map<String, Parameter> missingParameters;

    ValidatingConfigurationListener( Object mojo, MojoDescriptor mojoDescriptor, ConfigurationListener delegate )
    {
      this.mojo = mojo;
      this.delegate = delegate;
      this.missingParameters = new HashMap<>();

      if ( mojoDescriptor.getParameters() != null )
      {
        for ( org.apache.maven.plugin.descriptor.Parameter param : mojoDescriptor.getParameters() )
        {
          if ( param.isRequired() )
          {
            missingParameters.put( param.getName(), param );
          }
        }
      }
    }

    public Collection<org.apache.maven.plugin.descriptor.Parameter> getMissingParameters()
    {
      return missingParameters.values();
    }

    @Override
    public void notifyFieldChangeUsingSetter( String fieldName, Object value, Object target )
    {
      delegate.notifyFieldChangeUsingSetter( fieldName, value, target );

      if ( mojo == target )
      {
        notify( fieldName, value );
      }
    }

    @Override
    public void notifyFieldChangeUsingReflection( String fieldName, Object value, Object target )
    {
      delegate.notifyFieldChangeUsingReflection( fieldName, value, target );

      if ( mojo == target )
      {
        notify( fieldName, value );
      }
    }

    private void notify( String fieldName, Object value )
    {
      if ( value != null )
      {
        missingParameters.remove( fieldName );
      }
    }

  }

}
